import com.hb.domain.Spitter;
import com.hb.domain.Spittle;
import org.apache.commons.httpclient.HttpClient;
import org.springframework.context.annotation.Profile;
import org.springframework.http.*;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;

import java.net.URI;
import java.util.HashMap;
import java.util.Map;

public class FacebookDemo {
    /**
     * 仔细看一下 fetchFacebookProfile() 方法，你可能会发现方法中只有少量代码与获取 Facebook 个人信息直接相关。如果你要编写另
     * 一个方法来使用其他的 REST 资源，很可能会有很多代码是与 fetchFacebookProfile() 相同的。
     *
     * @param id
     * @return
     */
    public Profile fetchFacebookProfile(String id) {
        HttpClient client = HttpClients.createDefault();
        HttpGet request = new HttpGet("http://graph.facebook.com/" + id);
        request.setHeader("Accept", "application/json");
        HttpResponse response = client.executeMethod(request);
        HttpEntity entity = response.getEntity();
        ObjectMapper mapper = new ObjectMapper();
        return mapper.readValue(entity.getContent(), Profile.class);
    }

    /**
     * fetchFacebookProfile() 涉及十多行代码。通过使用 RestTemplate，现在减少到了几行。注意，在这个新版本的 fetchFacebookProfile() 中，我们没有
     * 使用字符串连接来构建 URL，而是利用了 RestTemplate 可以接受参数化 URL 这一功能。URL 中的 {id} 占位符最终将会用方法的 id 参数来填充。getForObject() 方法
     * 的最后一个参数是大小可变的参数列表，每个参数都会按出现顺序插入到指定 URL 的占位符中。
     *
     * @param id
     * @return
     */
    public Profile fetchFacebookProfile1(String id) {
        RestTemplate rest = new RestTemplate();
        return rest.getForObject("http://graph.facebook.com/{id}", Profile.class, id);
    }

    /**
     * 另外一种替代方案是将 id 参数放到 Map 中，并以 id 作为 key，然后将这个 Map 作为最后一个参数传递给 getForObject()
     *
     * @param id
     * @return
     */
    public Spittle[] fetchFacebookProfile2(String id) {
        Map<String, String> urlVariables = new HashMap<String, String>();
        urlVariables.put("id", id);
        RestTemplate rest = new RestTemplate();
        // 这里没有任何形式的 JSON 解析和对象映射。在表面之下，getForObject() 为我们将响应体转换为对象。它实现这些需
        // 要依赖 Spring 的 HTTP 消息转换器，与带有 @ResponseBody 注解的 Spring MVC 处理方法所使用的一样。
        // 这个方法也没有任何异常处理。这不是因为 getForObject() 不能抛出异常，而是因为它抛出的异常都是非检查型的。如果
        // 在 getForObject() 中有错误，将抛出非检查型 RestClientException 异常（或者它的一些子类）。如果愿意的话，
        // 你可以捕获它——但编译器不会强制你捕获它。
        return rest.getForObject("http://graph.facebook.com/{id}", Spittle[].class, urlVariables);
    }

    /**
     * 如果你对响应的 HTTP 状态码感兴趣，那么你可以调用 getStatusCode() 方法。例如，考虑下面这个获取 Spittle 对象的方法：
     * 在这里，如果服务器响应 304 状态，这意味着服务器端的内容自从上一次请求之后再也没有修改。在这种情况下，将会抛出自定义的
     * NotModifiedException 异常来表明客户端应该检查它的缓存来获取 Spittle。
     *
     * @param id
     * @return
     */
    public Spittle fetchSpittle(long id) {
        RestTemplate rest = new RestTemplate();
        ResponseEntity<Spittle> response = rest.getForEntity("http://localhost:8080/spittr-api/spittles/{id}", Spittle.class, id);
        if (response.getStatusCode() == HttpStatus.NOT_MODIFIED) {
            throw new NotModifiedException();
        }
        return response.getBody();
    }

    /**
     * 以下展现了如何使用基于 URI 版本的 put() 方法来更新服务器上的 Spittle 资源：
     *
     * @param spittle
     * @throws SpitterException
     */
    public void updateSpittle(Spittle spittle) throws SpitterException {
        RestTemplate rest = new RestTemplate();
        String url = "http://localhost:8080/spittr-api/spittles/" + spittle.getId();
        rest.put(URI.create(url), spittle);
    }

    /**
     * 从 getForObject() 和 getForEntity() 方法中我们也看到了，使用基于 String 的其他 put() 方法能够为我们减少创建 URI 的不便。
     * 这些方法可以将URI指定为模板并对可变部分插入值。以下是使用基于 String 的 put() 方法重写的 updateSpittle()：
     *
     * @param spittle
     * @throws SpitterException
     */
    public void updateSpittle1(Spittle spittle) throws SpitterException {
        RestTemplate rest = new RestTemplate();
        // 现在的 URI 使用简单的 String 模板来进行表示。当 RestTemplate 发送 PUT 请求时，URI 模板将 {id} 部分用 spittle.getId() 方法的
        // 返回值来进行替换。就像 getForObject() 和 getForEntity() 一样，这个版本的 put() 方法最后一个参数是大小可变的参数列表，每一个值会
        // 出现按照顺序赋值给占位符变量。
        rest.put("http://localhost:8080/spittr-api/spittles/{id}", spittle, spittle.getId());
    }

    /**
     * 还可以将模板参数作为 Map 传递进来：当使用 Map 来传递模板参数时，Map 条目的每个 key 值与 URI 模板中占位符变量的名字相同。
     * 在本示例中，它是一个 Spittle 对象。RestTemplate 将使用 Spring 中的某个 HTTP 消息转换器将 Spittle 对象转换为一种表述形式，
     * 并在请求体中将其发送到服务器端。
     *
     * @param spittle
     * @throws SpitterException
     */
    public void updateSpittle2(Spittle spittle) throws SpitterException {
        RestTemplate rest = new RestTemplate();
        Map<String, String> params = new HashMap<String, String>();
        params.put("id", String.valueOf(spittle.getId()));
        // 因为我们传递进来的是 Spittle 对象，所以需要一个能够处理任意对象的信息转换器。如果在类路径下包含 Jackson 2 库，那么 MappingJacksonHttpMessageConverter 将以
        // application/json 格式将 Spittle 写到请求中。
        rest.put("http://localhost:8080/spittr-api/spittles/{id}", spittle, params);
    }

    public void deleteSpittle(long id) {
        RestTemplate rest = new RestTemplate();
        rest.delete(URI.create("http://localhost:8080/spittr-api/spittles/" + id));
    }

    public void deleteSpittle1(long id) {
        RestTemplate rest = new RestTemplate();
        rest.delete("http://localhost:8080/spittr-api/spittles/{id}", id);
    }

    /**
     * 当 POST 新的 Spitter 资源到 Spitter REST API 时，它们应该发送到 http://localhost:8080/spittr-api/spitters，这里会有一个应对 POST 请求
     * 的处理方法来保存对象。因为这个 URL 不需要 URL 参数，所以我们可以使用任何版本的 postForObject()。但为了保持尽可能简单，我们可以这样调用：
     *
     * @param spitter
     * @return
     */
    public Spitter postSpitterForObject(Spitter spitter) {
        RestTemplate rest = new RestTemplate();
        return rest.postForObject("http://localhost:8080/spittr-api/spitters", spitter, Spitter.class);
    }

    /**
     * 假设除了要获取返回的 Spitter 资源，还要查看响应中 Location 头信息的值。在这种情况下，你可以这样调用 postForEntity()：
     * 就像 getForEntity() 方法一样，你可能想得到请求带回来的一些元数据。在这种情况下，postForEntity() 是更合适的方法。postForEntity() 方法有着与 postForObject() 几乎相同的一组签名。
     * 与 getForEntity() 方法一样，postForEntity() 返回一个 ResponseEntity<T> 对象。你可以调用这个对象的 getBody() 方法以获取资源对象（在本示例中是 Spitter）。getHeaders() 会给你一个 HttpHeaders，
     * 通过它可以访问响应中返回的各种 HTTP 头信息。这里，我们调用 getLocation() 来得到 java.net.URI 形式的 Location 头信息。
     *
     * @param spitter
     */
    public void usePostForEntity(Spitter spitter) {
        RestTemplate rest = new RestTemplate();
        ResponseEntity<Spitter> response = rest.postForEntity("http://localhost:8080/spittr-api/spitters", spitter, Spitter.class);
        Spitter responseSpitter = response.getBody();
        URI url = response.getHeaders().getLocation();
    }

    /**
     * 为了展示 postForLocation()，让我们再次 POST 一个 Spitter。这次，我们希望在返回中包含资源的 URL：
     * 在这里，我们以 String 的形式将目标 URL 传递进来，还有要 POST 的 Spitter 对象（在本示例中没有 URL 参数）。在创建资源后，如果服务端在响应的 Location 头信息
     * 中返回新资源的 URL，接下来 postForLocation() 会以 String 的格式返回该 URL。
     *
     * @param spitter
     * @return
     */
    public String postSpitter(Spitter spitter) {
        RestTemplate rest = new RestTemplate();
        return rest.postForLocation("http://localhost:8080/spittr-api/spitters", spitter).toString();
    }

    /**
     * 从服务器端获取 Spitter 资源的一种方式是使用 RestTemplate 的 getForEntity() 方法，但是 exchange() 也可以完成这项任务。通过传入 HttpMethod.GET 作为 HTTP 动作，我们会要
     * 求 exchange() 发送一个 GET 请求。第三个参数是用于在请求中发送资源的，但因为这是一个 GET 请求，它可以是 null。下一个参数表明我们希望将响应转换为 Spitter 对象。最后一个参数用
     * 于替换 URL 模板中 {spitter} 占位符的值。按照这种方式，exchange() 与之前使用的 getForEntity() 是几乎相同的，但是，不同于 getForEntity() —— 或 getForObject() —— exchange()
     * 方法允许在请求中设置头信息。
     * @param spitterId
     * @return
     */
    public Spitter useExchange(String spitterId) {
        RestTemplate rest = new RestTemplate();
        ResponseEntity<Spitter> responseEntity = rest.exchange("http://localhost:8080/spittr-api/spitters", HttpMethod.GET, null, Spitter.class, spitterId);
        return responseEntity.getBody();
    }

    /**
     * 不再给 exchange() 传递 null，而是传入带有请求头信息的 HttpEntity。
     * @param spitterId
     * @return
     */
    public Spitter useExchange1(String spitterId) {
        RestTemplate rest = new RestTemplate();
        // 设置请求头信息是很简单的，只需构造发送给 exchange() 方法的 HttpEntity 对象即可，HttpEntity 中包含承载头信息的 MultiValueMap：
        // 在这里，我们创建了一个 LinkedMultiValueMap 并添加值为 “application/json” 的 Accept 头信息。接下来，我们构建了一个 HttpEntity（使用 Object 泛型类型），将 MultiValueMap 作
        // 为构造参数传入。如果这是一个 PUT 或 POST 请求，我们需要为 HttpEntity 设置在请求体中发送的对象——对于 GET 请求来说，这是没有必要的。
        MultiValueMap<String, String> headers = new LinkedMultiValueMap<String, String>();
        headers.add("Accept", "application/json");
        HttpEntity<Object> requestEntity = new HttpEntity<>(headers);
        ResponseEntity<Spitter> responseEntity = rest.exchange("http://localhost:8080/spittr-api/spitters", HttpMethod.GET, requestEntity, Spitter.class, spitterId);
        return responseEntity.getBody();
    }
}
